package com.chinawayltd.ews.sample.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.chinawayltd.ews.sample.consts.TaskNodeConsts;
import com.chinawayltd.ews.sample.manager.openApi.CustomOpenApiManager;
import com.chinawayltd.ews.sample.mapper.*;
import com.chinawayltd.ews.sample.model.*;
import com.chinawayltd.ews.sample.service.DispatchAlgorithmService;
import com.chinawayltd.ews.sample.util.map.MapUtils;
import com.chinawayltd.ews.sample.utils.TaskDispathDateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @program: greeProject.timingTaskCenter
 * @description:
 * @author: xiazj
 * @create: 2018-05-25 13:08
 **/
@Service
public class DispatchAlgorithmServiceImpl implements DispatchAlgorithmService {

    private static final String TASK_PREFIX = "S";
    private static final Map<String, String> ERROR_CODE_CONSTANTS;
    static {
        ERROR_CODE_CONSTANTS = new HashMap<>();
        ERROR_CODE_CONSTANTS.put("01", "算法入口参数错误");
        ERROR_CODE_CONSTANTS.put("02", "算法初始解析生成失败");
        ERROR_CODE_CONSTANTS.put("03", "算法交叉变异错误");
    }

    @Autowired
    protected DispatchAlgorithmMapper dispatchAlgorithmMapper;
    @Autowired
    private CustomOpenApiManager customOpenApiManager;
    @Autowired
    private TInterfaceLogMapper tInterfaceLogMapper;
    @Autowired
    private TInterfaceParamMapper tInterfaceParamMapper;
    @Autowired
    private TTemplateMapper tTemplateMapper;
    @Autowired
    private TTemplateNodeMapper tTemplateNodeMapper;
    @Autowired
    private TTaskMapper tTaskMapper;
    @Autowired
    private TTaskNodeMapper tTaskNodeMapper;
    @Autowired
    private TTaskNodeExtendMapper tTaskNodeExtendMapper;
    @Autowired
    private TNodeOrderMapper tNodeOrderMapper;
    @Autowired
    private TTaskOrderMapper tTaskOrderMapper;
    @Autowired
    private TBranchMapper tBranchMapper;

    @Override
    @Transactional
    public void checkAndLogRunStatus() throws Exception {

        List<TInterfaceLog> unfinshedTaskes = dispatchAlgorithmMapper.getUnfinshedTask();
        if(unfinshedTaskes != null && !unfinshedTaskes.isEmpty()){
            Date now = new Date();
            for (TInterfaceLog unfinshedTaske : unfinshedTaskes) {
                String token = unfinshedTaske.getToken();
                //查询当前任务的运算进度
                Float runStatus = customOpenApiManager.milkRunStatus(token);
                if(runStatus != null){
                    //更新数据库中对应的运算任务进度
                    unfinshedTaske.setRateProgress((byte)(runStatus * 100));
                    if(runStatus >= 1) {
                        //获取接口数据
                        JSONObject data = customOpenApiManager.milkRunFetch(token);
                        if(data != null){
                            Byte isFinish = 1;
                            String errorCode = data.getString("ErrorCode");
                            if(errorCode == null){
                                //生成当前算法调度订单的运输任务
                                generateTaskData(data);
                            }else{
                                isFinish = 3;
                                unfinshedTaske.setErrorMsg(ERROR_CODE_CONSTANTS.get(errorCode));
                                //恢复当前任务调度的订单状态
                                dispatchAlgorithmMapper.initTaskGenerateStatusByLogId(unfinshedTaske.getId());
                            }

                            //更新算法调度日志信息
                            unfinshedTaske.setIsFinish(isFinish);
                            unfinshedTaske.setEndTime(now);

                            TInterfaceParamWithBLOBs param = new TInterfaceParamWithBLOBs();
                            param.setId(unfinshedTaske.getId());
                            param.setInterfaceResult(data.toJSONString());
                            param.setUpdateTime(now);
                            tInterfaceParamMapper.updateByPrimaryKeySelective(param);
                        }
                    }
                }

                unfinshedTaske.setUpdateTime(now);
                tInterfaceLogMapper.updateByPrimaryKeySelective(unfinshedTaske);
            }
        }

    }

    /**
     * 根据智能调度接口返回的结果转换成司机的任务流程
     * @param data
     */
    private void generateTaskData(JSONObject data) {

        //查询所有站点信息
        Map<Long, TBranch> branchMap = getBranchMap();
        //查询所有车辆的对应的任务模板
        Map<String, VehicleTaskTemplateVO> vehicleTaskTplMap = getVehicleTaskTemplateMap();
        //查询当前系统所有任务模板节点信息
        Map<Long, List<TTemplateNode>> templateInfo = getTemplateInfoMap();
        //查询所有车辆的对应的驾驶员信息
        Map<Long, List<VehicleDriverVO>> vehicleDriverMap = getVehicleDriverMap();

        int taskNo = 1;
        //通过接口返回的数据找到对应的任务模板，并生成任务流程
        for (String key : data.keySet()) {
            VehicleTaskTemplateVO vehicleTaskTpl = vehicleTaskTplMap.get(key);
            if (vehicleTaskTpl != null) {
                int taskIdx = 0;
                //找出当前车辆对应的任务模板信息
                List<TTemplateNode> templateNodes = templateInfo.get(vehicleTaskTpl.getTemplateId());
                JSONObject vehicleData = data.getJSONObject(key);
                JSONObject taskData = null;
                while ((taskData = vehicleData.getJSONObject(taskIdx + "")) != null) {
                    //创建任务对象并保存到数据库
                    TTask task = createTaskData(vehicleDriverMap, taskNo, vehicleTaskTpl, taskData);
                    //创建当前任务与订单的关联状态
                    createTaskOrderData(task, taskData);
                    //更新当前运输任务所关联的订单的生成状态
                    dispatchAlgorithmMapper.updateTaskGenerateStatus(task.getId());
                    //创建任务节点信息
                    createTaskNodeData(task, taskData, templateNodes, branchMap);
                    taskIdx++;
                    taskNo++;
                }
            }
        }
        //动态更新车辆自定义属性的车辆最早可用时间，把车辆最早可用时间设置为当前车辆最后一个任务的预计完成时间
        dispatchAlgorithmMapper.updateCarCarvldTime();
    }

    /**
     * 创建任务订单关系对象，并保存到数据
     * @param task
     * @param taskData
     */
    private void createTaskOrderData(TTask task, JSONObject taskData) {
        JSONArray orderArr = taskData.getJSONArray("task");
        Date now = new Date();
        //新增任务与订单关联表
        orderArr.forEach(orderObj ->{
            JSONObject order = (JSONObject) orderObj;
            TTaskOrder taskOrder = new TTaskOrder();
            taskOrder.setOrderId(order.getLong("task_id"));
            taskOrder.setTaskId(task.getId());
            taskOrder.setSamePallet(order.getShort("same_pallet"));
            taskOrder.setCreateTime(now);
            taskOrder.setUpdateTime(now);

            tTaskOrderMapper.insert(taskOrder);
        });
    }

    /**
     * 创建任务对象，并保存到数据
     * @param vehicleDriverMap
     * @param taskNo
     * @param vehicleTaskTpl
     * @param taskData
     * @return
     */
    private TTask createTaskData(Map<Long, List<VehicleDriverVO>> vehicleDriverMap, int taskNo, VehicleTaskTemplateVO vehicleTaskTpl, JSONObject taskData) {
        Date nowDate = new Date();
        LocalDateTime now = LocalDateTime.now();
        final String nowDateTimeStr = now.format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
        //运输任务对象
        TTask task = new TTask();
        task.setTaskNo(TASK_PREFIX + nowDateTimeStr + "-" + taskNo);
        task.setConfirmState((byte) 0);
        task.setExecutState((byte) 0);
        task.setCreateTime(nowDate);
        task.setUpdateTime(nowDate);

        //设置本躺任务板位总数
        Double routPallet = taskData.getDouble("rout_pallet");
        if(routPallet != null){
            task.setRoutPallet(new BigDecimal(routPallet));
        }

        //设置车辆信息
        task.setVehicleId(vehicleTaskTpl.getVehicleId());
        task.setVehicleNumber(vehicleTaskTpl.getCarNum());
        //设置任务预计开始和预计完成时间
        Double workStart = taskData.getDouble("rout_start");
        if (workStart != null) {
            task.setStartTime(TaskDispathDateUtils.formatCustomDate(workStart));
        }
        Double workEnd = taskData.getDouble("rout_end");
        if (workStart != null) {
            task.setFinishTime(TaskDispathDateUtils.formatCustomDate(workEnd));
        }
        //设置驾驶员信息
        VehicleDriverVO driverInfo = getTaskDriverInfo(task, vehicleDriverMap);
        if(driverInfo != null){
            task.setDriverId(driverInfo.getDriverId());
            task.setDriverName(driverInfo.getDriverName());
        }

        //设置本躺任务所运输的订单的申请部门
        task.setApplyDepartment(null);
        tTaskMapper.insert(task);
        return task;
    }

    /**
     * 生成任务流程节点数据
     * @param task
     * @param taskData
     * @param templateNodes
     */
    private void createTaskNodeData(TTask task, JSONObject taskData, List<TTemplateNode> templateNodes, Map<Long, TBranch> branchMap) {
        byte nodeNo = 1;
        //根据模板生成流程节点
        for (TTemplateNode templateNode : templateNodes) {
            Byte nodeType = templateNode.getNodeType();
            if(TaskNodeConsts.NODE_TYPE_LOADING == nodeType){
                byte loadindNodeLen = insertLoadingNode(templateNode, task, taskData, nodeNo, branchMap);
                nodeNo += loadindNodeLen;
            }else if(TaskNodeConsts.NODE_TYPE_UNLOADING == nodeType){
                byte unloadindNodeLen = insertUnLoadingNode(templateNode, task, taskData, nodeNo, branchMap);
                nodeNo += unloadindNodeLen;
            }else{//非装卸货节点
                insertRoutineTaskNode(templateNode, task, taskData, nodeNo);
                nodeNo++;
            }
        }
    }

    /**
     * 新增卸货任务节点
     * @param templateNode
     * @param task
     * @param taskData
     * @param nodeNo
     * @return
     */
    private byte insertUnLoadingNode(TTemplateNode templateNode, TTask task, JSONObject taskData, byte nodeNo, Map<Long, TBranch> branchMap) {
        JSONArray orderArr = taskData.getJSONArray("task");
        orderArr.sort((Object o1, Object o2) ->{
            JSONObject obj1 = (JSONObject)o1;
            JSONObject obj2 = (JSONObject)o2;
            Double arriveDelivery1 = obj1.getDouble("start_delivery");
            Double arriveDelivery2 = obj2.getDouble("start_delivery");
            if(arriveDelivery1 != null && arriveDelivery2 != null){
                return arriveDelivery1.compareTo(arriveDelivery2);
            }
            return 0;
        });

        Long lastDeliveryId = null;
        Double firstArriveDelivery = null;
        Set<Long> orderIds = new HashSet<>();
        byte nodeLen = 0;

        for (Object orderObj : orderArr) {
            JSONObject order = (JSONObject)orderObj;
            if(firstArriveDelivery == null)firstArriveDelivery = order.getDouble("start_delivery");
            //卸货点ID
            Long deliveryId = order.getLong("delivery");
            if(lastDeliveryId == null || lastDeliveryId == deliveryId){
                lastDeliveryId = deliveryId;
                //订单ID
                orderIds.add(order.getLong("task_id"));
            }else{
                insertExtendNode(templateNode, task, nodeNo, lastDeliveryId, firstArriveDelivery, orderIds, nodeLen, branchMap);
                nodeLen++;
                firstArriveDelivery = order.getDouble("start_delivery");
                lastDeliveryId = deliveryId;
                orderIds.clear();
                orderIds.add(order.getLong("task_id"));
            }
        }
        insertExtendNode(templateNode, task, nodeNo, lastDeliveryId, firstArriveDelivery, orderIds, nodeLen, branchMap);
        nodeLen++;
        return nodeLen;
    }

    /**
     * 新增装货任务节点
     * @param templateNode
     * @param task
     * @param taskData
     * @param nodeNo
     * @return
     */
    private byte insertLoadingNode(TTemplateNode templateNode, TTask task, JSONObject taskData, byte nodeNo, Map<Long, TBranch> branchMap) {
        JSONArray orderArr = taskData.getJSONArray("task");
        orderArr.sort((Object o1, Object o2) ->{
            JSONObject obj1 = (JSONObject)o1;
            JSONObject obj2 = (JSONObject)o2;
            Double arrivePickup1 = obj1.getDouble("start_pickup");
            Double arrivePickup2 = obj2.getDouble("start_pickup");
            if(arrivePickup1 != null && arrivePickup2 != null){
                return arrivePickup1.compareTo(arrivePickup2);
            }
            return 0;
        });

        Long lastPickupId = null;
        Double firstArrivePickup = null;
        Set<Long> orderIds = new HashSet<>();
        byte nodeLen = 0;
        for (Object orderObj : orderArr) {
            JSONObject order = (JSONObject)orderObj;
            if(firstArrivePickup == null)firstArrivePickup = order.getDouble("start_pickup");
            //装货点ID
            Long pickupId = order.getLong("pickup");
            if(lastPickupId == null || lastPickupId == pickupId){
                lastPickupId = pickupId;
                //订单ID
                orderIds.add(order.getLong("task_id"));
            }else{
                insertExtendNode(templateNode, task, nodeNo, lastPickupId, firstArrivePickup, orderIds, nodeLen, branchMap);
                nodeLen++;
                firstArrivePickup = order.getDouble("start_pickup");
                lastPickupId = pickupId;
                orderIds.clear();
                orderIds.add(order.getLong("task_id"));
            }
        }
        insertExtendNode(templateNode, task, nodeNo, lastPickupId, firstArrivePickup, orderIds, nodeLen, branchMap);
        nodeLen++;
        return nodeLen;
    }

    /**
     * 新增装卸货扩展节点信息到数据库，并增加对应的节点与订单的关联关系
     * @param templateNode
     * @param task
     * @param nodeNo
     * @param branchId
     * @param estimateFinishTimeNum
     * @param orderIds
     * @param nodeLen
     * @param branchMap
     */
    private void insertExtendNode(TTemplateNode templateNode, TTask task, byte nodeNo, Long branchId,
                                         Double estimateFinishTimeNum, Set<Long> orderIds,
                                         byte nodeLen, Map<Long, TBranch> branchMap) {
    	Date date = new Date();
    	TBranch branch = branchMap.get(branchId);
        String branchName = branch.getBranchName();
        //新增节点信息
        TTaskNode node = initTaskNode(templateNode, task, (byte) (nodeNo + nodeLen));
        node.setNodeName(node.getNodeName() + "(" + branchName + ")");
        if(estimateFinishTimeNum != null){
            node.setEstimateFinishTime(TaskDispathDateUtils.formatCustomDate(estimateFinishTimeNum));
        }
        tTaskNodeMapper.insert(node);
        
        //新增装卸货扩展表信息
        TTaskNodeExtend nodeEx = new TTaskNodeExtend();
        nodeEx.setId(node.getId());
        nodeEx.setBranchId(branchId);
        nodeEx.setCreateTime(date);
        nodeEx.setUpdateTime(date);
        tTaskNodeExtendMapper.insert(nodeEx);

        //新增装卸货扩展表与订单表关联信息
        orderIds.forEach(orderId -> {
            TNodeOrderKey nodeOrder = new TNodeOrderKey();
            nodeOrder.setOrderId(orderId);
            nodeOrder.setNodeId(node.getId());
            tNodeOrderMapper.insert(nodeOrder);
        });
    }

    /**
     * 新增非装卸货流程节点信息
     * @param templateNode
     * @param task
     * @param taskData
     * @return
     */
    private void insertRoutineTaskNode(TTemplateNode templateNode, TTask task, JSONObject taskData, byte nodeNo) {
        TTaskNode result = initTaskNode(templateNode, task, nodeNo);

        Date estimateFinishTime = null;
        Byte nodeType = templateNode.getNodeType();
        Byte tplNodeNo = templateNode.getNodeNo();
        switch (nodeType){
            case TaskNodeConsts.NODE_TYPE_TASK_START:
                estimateFinishTime = task.getStartTime();
                break;
            case TaskNodeConsts.NODE_TYPE_TASK_END:
                estimateFinishTime = task.getFinishTime();
                break;
            case TaskNodeConsts.NODE_TYPE_SETOUT:
                //车辆出发时间，
                // 如果是总装厂出发就取任务开始时间，
                // 如果是配件厂出发就取车辆离开本趟次任务最后一个取货点时间
                if(2 == tplNodeNo){//总装厂出发
                    estimateFinishTime = task.getStartTime();
                }else{
                    Double leavePickup = taskData.getDouble("leave_pickup");
                    if(leavePickup != null){
                        estimateFinishTime = TaskDispathDateUtils.formatCustomDate(leavePickup);
                    }
                }
                break;
            case TaskNodeConsts.NODE_TYPE_ARRIVE:
                //车辆到达时间
                //如果是从总装厂出发后到达的取车辆到达本趟次任务第一个取货点时间
                //如果是配件厂出发后到达的取车辆到达本趟次任务第一个卸货点时间
                if(3 == tplNodeNo){//总装厂出发后到达
                    Double arrivePickup = taskData.getDouble("arrive_pickup");
                    if(arrivePickup != null){
                        estimateFinishTime = TaskDispathDateUtils.formatCustomDate(arrivePickup);
                    }
                }else{
                    Double arriveDelivery = taskData.getDouble("arrive_delivery");
                    if(arriveDelivery != null){
                        estimateFinishTime = TaskDispathDateUtils.formatCustomDate(arriveDelivery);
                    }
                }
                break;
        }
        result.setEstimateFinishTime(estimateFinishTime);
        tTaskNodeMapper.insert(result);
    }

    private TTaskNode initTaskNode(TTemplateNode templateNode, TTask task, byte nodeNo) {
        Date now = new Date();
        TTaskNode result = new TTaskNode();
        result.setTaskId(task.getId());
        result.setNodeNo(nodeNo);
        result.setNodeType(templateNode.getNodeType());
        result.setNodeName(templateNode.getNodeName());
        result.setExecutState((byte)0);
        result.setCreateTime(now);
        result.setUpdateTime(now);
        return result;
    }

    /**
     * 根据当前任务的车辆执行时间找到对应的驾驶员信息
     * 规则： 任务开始时间在司机的上班时间段内，如果司机离下班时间还有一个
     * 小时以内就不用再分配运输任务，该躺任务由该车下个上班司机来执行
     * @param task
     * @param vehicleDriverMap
     * @return
     */
    private VehicleDriverVO getTaskDriverInfo(TTask task, Map<Long, List<VehicleDriverVO>> vehicleDriverMap) {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
        VehicleDriverVO result = null;
        Long vehicleId = task.getVehicleId();
        Double startTimeNums = (task.getStartTime() != null) ? TaskDispathDateUtils.parseCustomTime(sdf.format(task.getStartTime())) : null;
        if(startTimeNums != null){
            List<VehicleDriverVO> vehicleDriverList = vehicleDriverMap.get(vehicleId);
            if(vehicleDriverList != null){
                for (int idx = 0; idx < vehicleDriverList.size(); idx++) {
                    VehicleDriverVO vehicleDriver = vehicleDriverList.get(idx);
                    Double driverBeginTimeNums = TaskDispathDateUtils.parseCustomTime(vehicleDriver.getBigTime());
                    Double driverEndTimeNums = TaskDispathDateUtils.parseCustomTime(vehicleDriver.getEndTime());
                    //如果当前司机排班信息是跨天的，就把司机上班时间减12小时把下班时间加12小时等于把跨天的时间段整成同一天的时间
                    if(driverBeginTimeNums > driverEndTimeNums){//跨天的
                        driverBeginTimeNums -= 0.5d;
                        driverEndTimeNums += 0.5d;
                        //如果任务开始时间是比跨天的司机上班时间早,代表这个时间已经是第二天的时间范围所以要加上12小时.
                        //反之如果时间比司机上班司机晚证明时间还在当天内,就减12小时
                        startTimeNums = (startTimeNums < driverBeginTimeNums) ? startTimeNums + 0.5d : startTimeNums - 0.5d;
                    }
                    //判断当前任务预计开始时间是否离当前司机下班时间一个小时之内，如果是就把当前任务分配给当前车辆下一个班次的司机
                    Double difVal = driverEndTimeNums - startTimeNums;
                    if(difVal > 0 && difVal <= 0.041666666666666664){
                        result = vehicleDriverList.get((idx == (vehicleDriverList.size() - 1)) ? 0 : idx + 1);
                        break;
                    }else if(startTimeNums >= driverBeginTimeNums && startTimeNums <= driverEndTimeNums){
                        result = vehicleDriver;
                        break;
                    }
                }
            }
        }
        return result;
    }

    /**
     * 查询当前系统所有任务模板节点信息
     * @return
     */
    private Map<Long, List<TTemplateNode>> getTemplateInfoMap(){
        Map<Long, List<TTemplateNode>> result = new HashMap<>();
        List<TTemplate> templates = tTemplateMapper.selectByExample(new TTemplateExample());
        if(templates != null){
            templates.forEach(template -> {
                Long templateId = template.getId();
                TTemplateNodeExample nodeEx = new TTemplateNodeExample();
                nodeEx.setOrderByClause("node_no");
                nodeEx.createCriteria().andTemplateIdEqualTo(templateId);
                List<TTemplateNode> templateNodes = tTemplateNodeMapper.selectByExample(nodeEx);
                result.put(templateId, templateNodes);
            });
        }
        return result;
    }

    /**
     * 查询所有车辆的对应的任务模板
     * @return
     */
    private Map<String, VehicleTaskTemplateVO> getVehicleTaskTemplateMap (){
        Map<String, VehicleTaskTemplateVO> result = new HashMap<>();
        List<VehicleTaskTemplateVO> vehicleTaskTemplateList = dispatchAlgorithmMapper.getVehicleTaskTemplate();
        if(vehicleTaskTemplateList != null && !vehicleTaskTemplateList.isEmpty())
            result = MapUtils.listToMap(vehicleTaskTemplateList, "carNum");
        return result;
    }

    /**
     * 查询所有车辆的对应的驾驶员信息
     * @return
     */
    private Map<Long, List<VehicleDriverVO>> getVehicleDriverMap (){
        Map<Long, List<VehicleDriverVO>> result = new HashMap<>();
        List<VehicleDriverVO> vehicleDriverList = dispatchAlgorithmMapper.getVehicleDriver();
        vehicleDriverList.forEach(vehicleDriver -> {
            Long vehicleId = vehicleDriver.getVehicleId();
            List<VehicleDriverVO> driverList = result.get(vehicleId);
            if(driverList == null){
                driverList = new ArrayList<>();
                result.put(vehicleId, driverList);
            }
            driverList.add(vehicleDriver);
        });
        //排序
        for (Long vehicleId : result.keySet()) {
            List<VehicleDriverVO> driverList = result.get(vehicleId);
            driverList.sort((VehicleDriverVO o1, VehicleDriverVO o2) ->{
                Double o1Value = TaskDispathDateUtils.parseCustomTime(o1.getBigTime());
                Double o2Value = TaskDispathDateUtils.parseCustomTime(o2.getBigTime());
                if(o1Value != null && o2Value != null){
                    return o1Value.compareTo(o2Value);
                }
                return 0;
            });
        }
        return result;
    }

    private Map<Long,TBranch> getBranchMap() {
        Map<Long,TBranch> result = new HashMap<>();
        List<TBranch> branchList = tBranchMapper.selectByExample(new TBranchExample());
        if(branchList != null && !branchList.isEmpty()){
            branchList.forEach(branch -> {
                result.put(branch.getId(), branch);
            });
        }
        return result;
    }

}
